﻿/*
 * @author: wizardc
 */

using System;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

namespace Dou.Download
{
    /// <summary>
    /// 网络文件下载器
    /// </summary>
    public class WebDownload
    {
        private UnityWebRequest _request;
        private FileDownloadHandler _handler;

        private Action<string> _onComplete;
        private Action<string, long, long> _onProgress;
        private Action<string, DownloadErrorCode, string> _onError;

        private DownloadInfo _info;

        private DownloadState _state = DownloadState.Idle;
        private DownloadErrorCode _errorCode;
        private string _errorMsg;

        /// <summary>
        /// 下载信息
        /// </summary>
        public DownloadInfo info => _info;

        /// <summary>
        /// 当前下载状态
        /// </summary>
        public DownloadState state => _state;

        /// <summary>
        /// 错误码
        /// </summary>
        public DownloadErrorCode errorCode => _errorCode;

        /// <summary>
        /// 错误描述
        /// </summary>
        public string errorMsg => _errorMsg;

        /// <summary>
        /// 开始下载
        /// </summary>
        /// <param name="info">下载信息</param>
        /// <param name="onComplete"></param>
        /// <param name="onProgress"></param>
        /// <param name="onError"></param>
        public void Start(DownloadInfo info, Action<string> onComplete = null, Action<string, long, long> onProgress = null, Action<string, DownloadErrorCode, string> onError = null)
        {
            _info = info;

            _onComplete = onComplete;
            _onProgress = onProgress;
            _onError = onError;

            _state = DownloadState.Downloading;

            if (_info.totalBytes > 0)
            {
                DownloadBegin();
            }
            else
            {
                GetDownloadSize(_info.url, fileSize =>
                {
                    _info.totalBytes = fileSize;
                    DownloadBegin();
                }, errorMsg =>
                {
                    _state = DownloadState.Error;
                    _errorCode = DownloadErrorCode.GetFileSizeError;
                    _errorMsg = errorMsg;
                    _onError?.Invoke(_info.sign, _errorCode, _errorMsg);
                });
            }
        }

        private void GetDownloadSize(string url, Action<long> onComplete, Action<string> onError)
        {
            UnityWebRequest request = UnityWebRequest.Head(url);
            request.timeout = 10;
            UnityWebRequestAsyncOperation operation = request.SendWebRequest();
            operation.completed += asyncOperation =>
            {
                if (request.isHttpError || request.isNetworkError)
                {
                    onError?.Invoke(request.error);
                    return;
                }

                long fileSize = Convert.ToInt64(request.GetResponseHeader("Content-Length"));
                request.Dispose();
                onComplete?.Invoke(fileSize);
            };
        }

        private void DownloadBegin()
        {
            if (_info.currentBytes == _info.totalBytes)
            {
                _state = DownloadState.Done;
                _onComplete?.Invoke(_info.sign);
                return;
            }

            _handler = new FileDownloadHandler(_info, _onProgress);
            if (!_handler.CreateOrOpenStream())
            {
                _state = DownloadState.Error;
                _errorCode = DownloadErrorCode.OpenOrCreateTempFileError;
                _errorMsg = "Open or Cretae temp file error";
                _onError?.Invoke(_info.sign, _errorCode, _errorMsg);
                _handler.CloseStream();
                _handler = null;
                return;
            }

            _request = UnityWebRequest.Get(_info.url);
            _request.downloadHandler = _handler;
            // 设置请求数据范围
            if (_info.currentBytes == 0)
            {
                _request.SetRequestHeader("Range", "bytes=0-");
            }
            else
            {
                _request.SetRequestHeader("Range", "bytes=" + _info.currentBytes + "-" + _info.totalBytes);
            }
            // 开始请求
            UnityWebRequestAsyncOperation operation = _request.SendWebRequest();
            operation.completed += OnCompleted;
        }

        private void OnCompleted(AsyncOperation obj)
        {
            if (_request.isHttpError || _request.isNetworkError)
            {
                _state = DownloadState.Error;
                _errorCode = DownloadErrorCode.NetError;
                _errorMsg = _request.error;
                _onError?.Invoke(_info.sign, _errorCode, _errorMsg);
                return;
            }

            if (_info.currentBytes != _info.totalBytes)
            {
                _state = DownloadState.Error;
                _errorCode = DownloadErrorCode.FileSizeError;
                _errorMsg = $"Download size {_info.currentBytes} Total size {_info.totalBytes}";
                _onError?.Invoke(_info.sign, _errorCode, _errorMsg);
                return;
            }

            _handler.CloseStream();
            _handler = null;
            _request.Dispose();
            _request = null;

            if (MoveFileToDestination())
            {
                _state = DownloadState.Done;
                _onComplete(_info.sign);
            }
        }

        private bool MoveFileToDestination()
        {
            try
            {
                if (File.Exists(_info.filePath))
                {
                    _state = DownloadState.Error;
                    _errorCode = DownloadErrorCode.MoveFileToDestError;
                    _errorMsg = $"Move File Failure: Dest File \"{_info.filePath}\" has exists";
                    _onError?.Invoke(_info.sign, _errorCode, _errorMsg);
                    return false;
                }
                string tempPath = DownloadManager.GetTempPath(_info.sign);
                if (File.Exists(_info.filePath))
                {
                    File.Delete(_info.filePath);
                }
                File.Move(tempPath, _info.filePath);
                return true;
            }
            catch (Exception e)
            {
                _state = DownloadState.Error;
                _errorCode = DownloadErrorCode.MoveFileToDestError;
                _errorMsg = "Move File Failure: " + e.Message;
                _onError?.Invoke(_info.sign, _errorCode, _errorMsg);
                return false;
            }
        }

        /// <summary>
        /// 取消下载
        /// </summary>
        public void Cancel()
        {
            if (_handler != null)
            {
                _handler.Cancel();
                _onError?.Invoke(_info.sign, DownloadErrorCode.Cancel, "Cancel Download");
            }
        }
    }

    public class FileDownloadHandler : DownloadHandlerScript
    {
        private DownloadInfo _info;
        private Action<string, long, long> _onProgress;
        private FileStream _fileStream;
        private bool _cancel = false;

        public FileDownloadHandler(DownloadInfo info, Action<string, long, long> onProgress) : base(new byte[1024 * 1024])
        {
            _info = info;
            _onProgress = onProgress;
        }

        public bool CreateOrOpenStream()
        {
            try
            {
                string filePath = DownloadManager.GetTempPath(_info.sign);
                _fileStream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write);
                // 下标跳转到记录的地方
                _fileStream.Seek(_info.currentBytes, SeekOrigin.Begin);
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }

        protected override float GetProgress()
        {
            return (float)((double)(_info.currentBytes) / (double)_info.totalBytes);
        }

        protected override bool ReceiveData(byte[] data, int dataLength)
        {
            if (data == null || dataLength == 0 || _fileStream == null || _cancel)
            {
                return false;
            }

            try
            {
                // 将接收的数据写入数据流
                _fileStream.Write(data, 0, dataLength);
                _info.currentBytes = _fileStream.Position;
                // 回调
                _onProgress?.Invoke(_info.sign, _info.currentBytes, _info.totalBytes);
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }

        protected override void CompleteContent()
        {
            base.CompleteContent();
            CloseStream();
        }

        public void Cancel()
        {
            _cancel = true;
        }

        public void CloseStream()
        {
            if (_fileStream != null)
            {
                _fileStream.Close();
                _fileStream.Dispose();
                _fileStream = null;
            }
        }
    }
}
